#include <gtkmm.h>
#include <libglademm.h>
#include <pthread.h>
#include <iostream>
#include <string>
#include <list>
#include "cluster.h"
#include "node.h"

using namespace std;

Cluster *cluster;
list<string*> console;


class NodeColumnModel : public Gtk::TreeModelColumnRecord {
	public:
	Gtk::TreeModelColumn<Glib::ustring> node_name;
	Gtk::TreeModelColumn<Glib::ustring> node_ip;
	Gtk::TreeModelColumn<bool> node_used;
	Gtk::TreeModelColumn<Glib::RefPtr<Gdk::Pixbuf> > node_online;
	Gtk::TreeModelColumn<bool> node_notexpired;
	NodeColumnModel() {
		add(node_online);
		add(node_used);
		add(node_name);
		add(node_ip);
		add(node_notexpired);
	}
};

class NodeModel : public Gtk::ListStore {
	private:
	list<Node*> *nodes;
	NodeColumnModel *model;
	Glib::RefPtr<Gdk::Pixbuf> img_on;
	Glib::RefPtr<Gdk::Pixbuf> img_off;
	public:
	NodeModel(NodeColumnModel *ncm, list<Node*> *the_nodes) : ListStore(*ncm) {
		model = ncm;
		nodes = the_nodes;
		img_on = Gdk::Pixbuf::create_from_file("on.png");
		img_off = Gdk::Pixbuf::create_from_file("off.png");
		refreshAll();
	};
	void refreshAll() {
		clear();
		list<Node*>::iterator n;
		for (n=nodes->begin(); n!=nodes->end(); n++) {
			Gtk::TreeModel::Row curNode = *append();
			curNode[model->node_name] = (*n)->getHostname().c_str();
			curNode[model->node_ip] = (*n)->getIP().c_str();
			curNode[model->node_used] = (*n)->isActive();
			curNode[model->node_online] = ((*n)->isOnline() ? img_on : img_off );
			curNode[model->node_notexpired] = true;
		}
	}
};

// global vars for utility's sake
Glib::RefPtr<Gnome::Glade::Xml> glade;
NodeModel *nodeModel;
Gtk::TreeView *listView;
NodeColumnModel *nodeColumns;
list<Node*> *nodes;
bool busy = false;
string* poweroff;
string* command_field;

// worker threads
pthread_t *refresher;
pthread_t *pulser;
pthread_t *executor;

// dialogs
Gtk::AboutDialog *dlg_about = 0;

// buttons and menu items
Gtk::Button *btn_exec = 0;
Gtk::ToolButton *btn_add = 0;
Gtk::ToolButton *btn_remove = 0;
Gtk::ToolButton *btn_refresh = 0;
Gtk::ToolButton *btn_shutdown = 0;
Gtk::ProgressBar *progressbar = 0;
Gtk::ImageMenuItem *mnu_sel_online = 0;
Gtk::ImageMenuItem *mnu_sel_offline = 0;
Gtk::ImageMenuItem *mnu_sel_inverse = 0;
Gtk::ImageMenuItem *mnu_sel_none = 0;
Gtk::ImageMenuItem *mnu_file_export = 0;
Gtk::ImageMenuItem *mnu_file_quit = 0;
Gtk::ImageMenuItem *mnu_hlp_about = 0;
Gtk::Entry *gui_command_field = 0;
Gtk::TextView *gui_console_out = 0;
Glib::RefPtr<Gtk::TextBuffer> output;

// event handlers
void btn_remove_click() {std::cout << "remove clicked" << std::endl;}
void btn_add_click() {std::cout << "add clicked" << std::endl;}

void mnu_sel_inverse_click() {
	list<Node*>::iterator n;
	for(n = nodes->begin(); n != nodes->end(); n++) {
		Node *node = (*n);
		node->setActive(!node->isActive());
	} nodeModel->refreshAll();
}

void mnu_sel_online_click() {
	list<Node*>::iterator n;
	for(n = nodes->begin(); n != nodes->end(); n++) {
		Node *node = (*n);
		if(node->isOnline()) {
			node->setActive(true);
		} else {
			node->setActive(false);
		}
	} nodeModel->refreshAll();
}

void mnu_sel_offline_click() {
	list<Node*>::iterator n;
	for(n = nodes->begin(); n != nodes->end(); n++) {
		Node *node = (*n);
		if(!node->isOnline()) {
			node->setActive(true);
		} else {
			node->setActive(false);
		}
	} nodeModel->refreshAll();
}

void mnu_sel_none_click() {
	list<Node*>::iterator n;
	for(n = nodes->begin(); n != nodes->end(); n++) {
		Node *node = (*n);
		node->setActive(false);
	} nodeModel->refreshAll();
}

void mnu_file_quit_click() {
	Gtk::Main::quit();
}

void mnu_file_export_click() {
	cout << "TODO: create export dialog or submenu" << endl;
}

void mnu_hlp_about_click() {
	dlg_about->run();
	dlg_about->hide();
}

void nodelist_changed(const Gtk::TreeModel::Path& path, const Gtk::TreeModel::iterator& iter) {
	Gtk::TreeRow row = *iter;
	if(row[nodeColumns->node_notexpired]) {
		string ip = ((Glib::ustring)row[nodeColumns->node_ip]).raw();
		cluster->setNodeActive(&ip,row[nodeColumns->node_used]);
	}
}

void *show_progress(void*) {
	progressbar->set_fraction(0.1);
	while(busy) {
		// there's a GTK problem associated with the pulsing...
		// progressbar->pulse();
		sleep(1);
	} progressbar->set_fraction(0.0);
}

void *do_node_refresh(void*) {
	cluster->probe();
	nodeModel->refreshAll();	
	busy = false;
	btn_refresh->set_sensitive(true);
	btn_exec->set_sensitive(true);
}

void *do_execute(void* cmd) {
	string *command = (string*)cmd;
	cluster->execute(cluster->getActiveNodes(),command);
	list<string*>::iterator s;
	// this may need some work (delete list items)
	for(s = console.begin(); s != console.end(); s++) {
		output->insert(output->end(),Glib::ustring((*s)->c_str()));
	} busy = false;
	btn_refresh->set_sensitive(true);
	btn_exec->set_sensitive(true);
}

void execute_command(string* cmd) {
	busy = true;
	btn_refresh->set_sensitive(false);
	btn_exec->set_sensitive(false);
	cout << "$$ Executing: " << *cmd << endl;
	pthread_create(executor,NULL,do_execute,(void*)cmd);
	pthread_create(pulser,NULL,show_progress,NULL);
}

void btn_exec_click() {
	delete command_field;
	command_field = new string(gui_command_field->get_text().raw());
	execute_command(command_field);
}

void btn_shutdown_click() {
	execute_command(poweroff);
}

void btn_refresh_click() {
	busy = true;
	btn_refresh->set_sensitive(false);
	btn_exec->set_sensitive(false);
	pthread_create(refresher,NULL,do_node_refresh,NULL);
	pthread_create(pulser,NULL,show_progress,NULL);
}

void register_handlers() {
	// connect execute signal
	glade->get_widget("btn_exec",btn_exec);
	btn_exec->signal_clicked().connect(sigc::ptr_fun(btn_exec_click));
	// connect toolbar signals
	glade->get_widget("btn_refresh", btn_refresh);
	btn_refresh->signal_clicked().connect(sigc::ptr_fun(btn_refresh_click));
	glade->get_widget("btn_shutdown", btn_shutdown);
	btn_shutdown->signal_clicked().connect(sigc::ptr_fun(btn_shutdown_click));
	glade->get_widget("btn_remove", btn_remove);
	btn_remove->signal_clicked().connect(sigc::ptr_fun(btn_remove_click));
	glade->get_widget("btn_add", btn_add);
	btn_add->signal_clicked().connect(sigc::ptr_fun(btn_add_click));
	// connect menu signals
	// format:
	// glade->get_widget("button",button);
	// button->signal_activate().connect(sigc::ptr_fun(button_click));
	glade->get_widget("mnu_sel_online",mnu_sel_online);
	mnu_sel_online->signal_activate().connect(sigc::ptr_fun(mnu_sel_online_click));
	glade->get_widget("mnu_sel_offline",mnu_sel_offline);
	mnu_sel_offline->signal_activate().connect(sigc::ptr_fun(mnu_sel_offline_click));
	glade->get_widget("mnu_sel_inverse",mnu_sel_inverse);
	mnu_sel_inverse->signal_activate().connect(sigc::ptr_fun(mnu_sel_inverse_click));
	glade->get_widget("mnu_sel_none",mnu_sel_none);
	mnu_sel_none->signal_activate().connect(sigc::ptr_fun(mnu_sel_none_click));
	glade->get_widget("mnu_file_quit",mnu_file_quit);
	mnu_file_quit->signal_activate().connect(sigc::ptr_fun(mnu_file_quit_click));
	glade->get_widget("mnu_file_export",mnu_file_export);
	mnu_file_export->signal_activate().connect(sigc::ptr_fun(mnu_file_export_click));
	glade->get_widget("mnu_hlp_about",mnu_hlp_about);
	mnu_hlp_about->signal_activate().connect(sigc::ptr_fun(mnu_hlp_about_click));
	// get the command entry field and console
	glade->get_widget("gui_command_field",gui_command_field);
	glade->get_widget("gui_console_out",gui_console_out);
	// create text buffer for output
	output = Gtk::TextBuffer::create();
	gui_console_out->set_buffer(output);
	// connect node list signals
	nodeModel->signal_row_changed().connect(sigc::ptr_fun(nodelist_changed));
	// create progress bar
	glade->get_widget("gui_progressbar",progressbar);
	progressbar->set_pulse_step(0.1);
}

void create_and_show_gui(int argc, char *argv[]) {
	Gtk::Main program(argc, argv);
	try {
		// create the glade GUI
	    glade = Gnome::Glade::Xml::create("cluster.glade");
	    // make the node model
		// assign the model to the list
		glade->get_widget("gui_node_view",listView);
		nodeColumns = new NodeColumnModel();
		nodeModel = new NodeModel(nodeColumns,nodes);
		Glib::RefPtr<Gtk::TreeModel> store;
		store = Glib::RefPtr<Gtk::TreeModel>(nodeModel);
		listView->set_model(store);
		// create node view columns
		listView->append_column("",nodeColumns->node_online);
		listView->append_column_editable("",nodeColumns->node_used);
		listView->append_column("Node",nodeColumns->node_name);
		listView->append_column("IP",nodeColumns->node_ip);
		// register handlers
		register_handlers();
		// start the program
		Gtk::Window *window;
		glade->get_widget("window",window);
		glade->get_widget("dlg_about", dlg_about);
		program.run(*window);
	} catch(const Gnome::Glade::XmlError& ex) {
		std::cerr << ex.what() << std::endl;
    	std::exit(1);
	} catch(void *x) {
		std::cout << "Caught error." << std::endl;
	}
}

void initialize() {
	console = list<string*>();
	cluster = new Cluster(string("nodes.data"),true,&console);
	nodes = cluster->getNodes();
	pulser = new pthread_t;
	executor = new pthread_t;
	refresher = new pthread_t;
	poweroff = new string("ssh root@$host poweroff");
	command_field = new string("dummy");
}

int main(int argc, char *argv[]) {
	initialize();
	create_and_show_gui(argc, argv);
    return 0;
}

